package sanshui.system.trade.factory.data;

import cn.hutool.core.bean.BeanUtil;
import org.apache.tomcat.jni.Local;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sanshui.system.trade.config.SpringContextHolder;
import sanshui.system.trade.db.entity.StockTblFavoriteBasic;
import sanshui.system.trade.db.entity.TradeTargetCode;
import sanshui.system.trade.factory.timeManager.TimeManager;
import sanshui.system.trade.factory.trade.worker.AbstractTradeWorker;
import sanshui.system.trade.model.DataLoadModel;
import sanshui.system.trade.model.StockInfoModel;
import sanshui.system.trade.model.TradeTimeDriverModel;
import sanshui.system.trade.pojo.StockInfo;
import sanshui.system.trade.pojo.StockInfoVo;
import sanshui.system.trade.util.CommonTimeUtil;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;

/**
 * 时间驱动
 * 通过时间来获取数据
 * */
public class DataCache {
    private static final Logger logger = LoggerFactory.getLogger(DataCache.class);

    DataFactory dataFactory = SpringContextHolder.getBean(DataFactory.class);


    // 交易系统类型：实时、回测
    TradeSystemType tradeSystemType;

    // 交易市场：A股、OKEX
    TradeMarketKey tradeMarketKey;

    // 交易市场的数据类型: T0\T1
    DataMarketType dataMarketType;

    // 最近一个时间的目标代码股票信息
    Map<String, Bar> currentBar = new HashMap<>();
    // 指标数据集合，保存本次worker指标实时计算结果 Key: 指标+_+n value:历史计算的实时指标
    final Map<String, List<String>> indicatorsMap = new HashMap<>();
    // 有特定长度的存前30天历史, key:code, value:
    final Map<String, List<Bar>> hisPriceDataMap = new HashMap<>();
    // 所有目标代码的历史信息
    Map<String, KLineData> allData = new HashMap<>();

    // 所有目标代码
    List<String> targetCodes = new ArrayList<>();

    // 1m,5m,1d
    String frequency;

    //在实时系统时，这个时间只是用来取得历史数据，方便指标计算
    LocalDateTime startTime;
    LocalDateTime endTime;

    Long workerId;
    Long accId;
    Long userId;

    /**
     * 创建时需要初始化
     * 确定需要的股票数量，历史时间范围，数据频率
     * 需要根据交易系统，创建时间线：A股、加密等都不一样
     * 如果是回测系统，时间线可以固定；实时系统，时间线需要实时计算后获取
     * */
    public DataCache(DataLoadModel dataLoadModel) throws Exception {
        this.targetCodes = new ArrayList<>();
        if(dataLoadModel.getTargetCodes() != null && !dataLoadModel.getTargetCodes().isEmpty()){
            logger.info("缓存初始化, targetCodes:{}", targetCodes.size());
            this.setTargetCodes(dataLoadModel.getTargetCodes());
        }

        if (TradeSystemType.BACK_TRADE.compareTo(dataLoadModel.getTradeSystemType()) == 0 &&
                (null == dataLoadModel.getTargetCodes() || dataLoadModel.getTargetCodes().isEmpty())){
            logger.info("没有输入数据，使用自选进行回测");
            List<TradeTargetCode> targetCodeAgu = dataFactory.getTargetCodeAgu(dataLoadModel.getWorkerId());
            targetCodeAgu.addAll(this.parseCodeStrToCodeList());
            for (TradeTargetCode tradeTargetCode : targetCodeAgu) {
                this.targetCodes.add(tradeTargetCode.getTsCode());
            }
        }

        if (TradeSystemType.RT.compareTo(dataLoadModel.getTradeSystemType()) == 0 &&
        this.getTargetCodes().isEmpty()
        ){
            List<TradeTargetCode> targetCodeAgu = dataFactory.getTargetCodeAgu(dataLoadModel.getWorkerId());
            logger.warn("当前实时系统未给定目标，使用数据库目标:{}", targetCodeAgu.size());
            targetCodeAgu.addAll(this.parseCodeStrToCodeList());
            for (TradeTargetCode tradeTargetCode : targetCodeAgu) {
                this.targetCodes.add(tradeTargetCode.getTsCode());
            }
        }

        if (this.targetCodes.isEmpty()){
            throw new Exception("没有标的");
        } else {
            logger.info("目标标的数量:{}", this.targetCodes.size());
        }

        setDataMarketType(dataLoadModel.getDataMarketType());
        setTradeSystemType(dataLoadModel.getTradeSystemType());
        setTradeMarketKey(dataLoadModel.getTradeMarket());
        setFrequency(dataLoadModel.getFrequency());
        setStartTime(dataLoadModel.getStartTime());
        setEndTime(dataLoadModel.getEndTime());
        setUserId(dataLoadModel.getUserId());
        setWorkerId(dataLoadModel.getWorkerId());
        setAccId(dataLoadModel.getAccId());
    }

    /**
     * 获取历史数据
     * 制作时间驱动器
     * */
    public void loadHisData(TimeManager timeManager, Function<StockInfoModel, Bar> parseStockValue){
        String startTimeStr = CommonTimeUtil.getNowTime8(this.startTime);
        String endTimeStr = CommonTimeUtil.getNowTime8(this.endTime);
        logger.info("【开始】加载所有数据, start_date:{}, end_date:{}", startTimeStr, endTimeStr);
        // 加载交易日历数据，用于驱动时间创建
        if ("1d".equalsIgnoreCase(frequency) ){
            List<TradeTimeDriverModel> driverTimes = dataFactory.getDriverTimeIn1Day(
                    startTimeStr,
                    endTimeStr,
                    "144000");
            timeManager.init(driverTimes);
        }
        // 利用已有数据初始化时间驱动器
        // 把每个目标的数据加载到内存
        this.initLoadDataToCache(parseStockValue);
    }
    /**
     * 初始化数据到内存
     * 由外部决定存入到数据格式
     * */
    private void initLoadDataToCache(Function<StockInfoModel, Bar> parseStockValue){
        // 把每个目标的数据加载到内存
        for (String targetCode : this.targetCodes) {
            List<StockInfoVo> hisStockInfo = dataFactory.getHisStockInfo(
                    targetCode,
                    CommonTimeUtil.getNowTime8(this.startTime),
                    CommonTimeUtil.getNowTime8(this.endTime));
            List<StockInfo> stockInfos = this.buildAGuStockInfoList(hisStockInfo);
            KLineData kLineData = new KLineData();
            for (StockInfo stockInfo : stockInfos) {
                StockInfoModel stockInfoModel = new StockInfoModel();
                BeanUtil.copyProperties(stockInfo, stockInfoModel);
                LocalDateTime timestamp = CommonTimeUtil.getTimestamp8(stockInfo.getTradeTime());
                stockInfoModel.setTimestamp(timestamp);
                Bar bar = parseStockValue.apply(stockInfoModel);
                kLineData.addBar(frequency, bar);
            }
            allData.put(targetCode, kLineData);
        }
    }

    public void update(LocalDateTime time){
        this.parseCurrentMap(time);
    }

    /**
     * 从allData取出当前时间到数据
     * 回测系统没有取到，且时间结果，返回false
     * */
    public boolean update(LocalDateTime currentTime, TradeSystemType systemType){
        if (TradeSystemType.BACK_TRADE.compareTo(systemType) == 0){
            this.parseCurrentMap(currentTime); // 从缓存到K线中取出数据
            if (this.currentBar == null && !currentTime.isBefore(endTime)){
                logger.info("当前时间数据Map为空, 且当前时间大于等于结束时间, currentTime:{}, endTime:{}", currentTime, endTime);
                return false;
            }
            return true;
        }
        if (TradeSystemType.RT.compareTo(systemType) == 0){
            // 取出实时数据--交给外部脚本做更新了，实际上这里去做实时更新到缓存
            return true;
        }
        return false;
    }

    /**
     * 更新实时交易系统的数据
     * 如果没有拿到数据需要等待, return true
     * */
    public boolean updateInRtWait(LocalDateTime time, Function<StockInfoModel, Bar> parseStockValue){
        currentBar = new HashMap<>();

        if (allData.isEmpty()){
            // 需要获取实时数据
            List<StockInfo> stockInfosRt = dataFactory.getStockInfoRt(targetCodes, time, tradeMarketKey);
            if (null == stockInfosRt || stockInfosRt.size() < targetCodes.size()){
                return true;
            }
            currentBar = this.parseInfoByWorkerToBar(stockInfosRt, parseStockValue);
            return false;
        } else {
            this.parseCurrentMap(time);
            return false;
        }
    }

    public void addBar(Bar bar) {
        allData.computeIfAbsent(bar.getSymbol(), k -> new KLineData()).getBars().put(bar.getTimestamp(), bar);
    }

    public Bar getBars(String symbol, LocalDateTime timestamp) {
        KLineData kLineData = allData.get(symbol);
        if (kLineData == null) return null;
        return kLineData.getBars().get(timestamp);
    }

    public List<Bar> getBars(String symbol, LocalDateTime start, LocalDateTime end) {
        KLineData kLineData = allData.get(symbol);
        if (kLineData == null) return Collections.emptyList();
        return new ArrayList<>(kLineData.getBars().subMap(start, true, end, true).values());
    }

    private void parseCurrentMap(LocalDateTime timestamp){
        for (String targetCode : targetCodes) {
            KLineData kLineData = allData.get(targetCode);
            if (null != kLineData){
                Bar nearestBar = kLineData.getNearestBar(timestamp);
                if (null == nearestBar) {
                    currentBar = null;
                    return;
                }
                if (currentBar == null){
                    currentBar = new HashMap<>();
                }
                currentBar.put(targetCode, nearestBar);
            }
        }
    }

    private Map<String, Bar> parseInfoByWorkerToBar(List<StockInfo> hisStockInfo,
                                                    Function<StockInfoModel, Bar> parseStockValue){
        Map<String, Bar> bars = new HashMap<>();
        for (StockInfo stockInfo : hisStockInfo) {
            StockInfoModel stockInfoModel = new StockInfoModel();
            BeanUtil.copyProperties(stockInfo, stockInfoModel);
            Bar bar = parseStockValue.apply(stockInfoModel);
            bars.put(stockInfo.getTsCode(), bar);
        }
        return bars;
    }

    public void clear(){
        currentBar = new HashMap<>();
        allData  = new HashMap<>();
    }

    public void clearDeep(){
        currentBar = null;
        allData = null;
        targetCodes = null;
    }

    public TradeSystemType getTradeSystemType() {
        return tradeSystemType;
    }

    public void setTradeSystemType(TradeSystemType tradeSystemType) {
        this.tradeSystemType = tradeSystemType;
    }

    public TradeMarketKey getTradeMarketKey() {
        return tradeMarketKey;
    }

    public void setTradeMarketKey(TradeMarketKey tradeMarketKey) {
        this.tradeMarketKey = tradeMarketKey;
    }

    public Map<String, Bar> getcurrentBar() {
        return currentBar;
    }

    public void setcurrentBar(Map<String, Bar> currentBar) {
        this.currentBar = currentBar;
    }


    public DataMarketType getDataMarketType() {
        return dataMarketType;
    }

    public void setDataMarketType(DataMarketType dataMarketType) {
        this.dataMarketType = dataMarketType;
    }

    public List<String> getTargetCodes() {
        return targetCodes;
    }

    public void setTargetCodes(List<String> targetCodes) {
        this.targetCodes = targetCodes;
    }

    public String getFrequency() {
        return frequency;
    }

    public void setFrequency(String frequency) {
        this.frequency = frequency;
    }

    public LocalDateTime getStartTime() {
        return startTime;
    }

    public void setStartTime(LocalDateTime startTime) {
        this.startTime = startTime;
    }

    public LocalDateTime getEndTime() {
        return endTime;
    }

    public void setEndTime(LocalDateTime endTime) {
        this.endTime = endTime;
    }

    private List<StockInfo> buildAGuStockInfoList(List<StockInfoVo> his){
        List<StockInfo> res = new ArrayList<>();
        for (StockInfoVo hi : his) {
            StockInfo stockInfo = new StockInfo();
            stockInfo.setLow((long) (hi.getLow()*100));
            stockInfo.setOpen((long) (hi.getOpen()*100));
            stockInfo.setHigh((long) (hi.getHigh()*100));
            stockInfo.setPrice((long) (hi.getPrice()*100));
            stockInfo.setClose((long) (hi.getClose()*100));
            stockInfo.setChange((long) (hi.getChange()*100));
            stockInfo.setPctChange((long) (hi.getPctChange()*100));
            stockInfo.setVol((long) (hi.getVol()*100));
            stockInfo.setDataSrc(hi.getDataSrc());
            stockInfo.setTradeDate(hi.getTradeDate());
            stockInfo.setTsName(hi.getTsName());
            stockInfo.setTsCode(hi.getTsCode().toUpperCase());
            stockInfo.setTradeTime(hi.getTradeTime());
            stockInfo.setAmount((long) (hi.getAmount()*100));
            res.add(stockInfo);
        }
        return res;
    }

    private List<TradeTargetCode> parseCodeStrToCodeList(){
        List<TradeTargetCode> res = new ArrayList<>();
        for (String targetCode : this.targetCodes) {
            TradeTargetCode code = new TradeTargetCode();
            code.setTsCode(targetCode);
            res.add(code);
        }
        return res;
    }

    public boolean isWeekend(LocalDateTime currentTime){
        return dataFactory.isWeekend(currentTime);
    }

    public void setDataFactory(DataFactory dataFactory) {
        this.dataFactory = dataFactory;
    }

    public void setWorkerId(Long workerId) {
        this.workerId = workerId;
    }

    public void setAccId(Long accId) {
        this.accId = accId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public Map<String, List<String>> getIndicatorsMap() {
        return indicatorsMap;
    }

    public Map<String, List<Bar>> getHisPriceDataMap() {
        return hisPriceDataMap;
    }
}
